By: Iñigo Expósito, Gerardo Gómez Argüelles, Elisa Scocco

Introduction

Africa’s rich natural resources have long been both a blessing and a curse. Even if mineral wealth holds the potential to drive economic growth, it has often coincided with the outbreak and persistence of violent conflicts. This research investigates the spatial relationship between natural resource deposits, particularly minerals and oil, and armed conflict across the African continent.

We aim to answer a core geospatial question: are conflicts in Africa more likely to occur near areas rich in natural resources?

In doing so, we use geospatial data techniques to identify whether proximity to high-value resources predicts conflict origination, intensity, or persistence.

Motivation and Context: The Democratic Republic of Congo

The ongoing crisis in the Democratic Republic of Congo (DRC) provides a compelling case study of how spatial access to natural resources can fuel and prolong conflict. The eastern provinces of the DRC, particularly North and South Kivu, are rich in minerals such as coltan, gold, and tin, all of which are essential for global electronic supply chains. According to UNHCR, armed groups in the region routinely fight for control of these resource-rich areas, leading to violence and massive displacement.

Moreover, as reported by France24, the recent surge in hostilities is closely tied to the economic incentives offered by these mineral deposits. Control over mining sites translates into financial power for militias, reinforcing a cycle of conflict that is both economically and geographically grounded.

These accounts emphasize why it is critical to study the spatial correlation between conflict and resource-rich territories, not only in the DRC but across the African continent.

Research Questions

This study aims to address the following questions:

1.- Conflict Localization: Are conflicts disproportionately located near mineral or oil extraction sites?

2.- Predictive Power: Can proximity to resources be used to predict future outbreaks or escalations of conflict?

3.- Conflict Intensity: Do areas with more abundant or higher-value resources experience more severe or persistent violence?

Africa Conflict Analysis

This section explores the spatial and temporal patterns of conflict events in Africa between 1997 and 2017. Utilizing the Armed Conflict Location and Event Data Project (ACLED) dataset and geospatial analytical techniques implemented in R, we systematically examine how conflict events are distributed geographically, how their intensity varies, and how these patterns evolve over time. Through this preliminary spatial investigation, we seek to lay a robust foundation for deeper analyses, highlighting key regions and temporal dynamics that define Africa’s contemporary conflict landscape.

library(rnaturalearth)
library(rnaturalearthdata)
## 
## Attaching package: 'rnaturalearthdata'
## The following object is masked from 'package:rnaturalearth':
## 
##     countries110
library(sf)
## Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(stringr)
library(spData)
## To access larger datasets in this package, install the spDataLarge
## package with: `install.packages('spDataLarge',
## repos='https://nowosad.github.io/drat/', type='source')`
library(terra)
## terra 1.8.29
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats   1.0.0     ✔ readr     2.1.5
## ✔ lubridate 1.9.4     ✔ tibble    3.2.1
## ✔ purrr     1.0.4     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::extract() masks terra::extract()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(exactextractr)
library(gdistance)
## Loading required package: raster
## Loading required package: sp
## 
## Attaching package: 'raster'
## 
## The following object is masked from 'package:dplyr':
## 
##     select
## 
## Loading required package: igraph
## 
## Attaching package: 'igraph'
## 
## The following object is masked from 'package:raster':
## 
##     union
## 
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## 
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## 
## The following object is masked from 'package:tidyr':
## 
##     crossing
## 
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## 
## The following objects are masked from 'package:terra':
## 
##     blocks, compare, union
## 
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## 
## The following object is masked from 'package:base':
## 
##     union
## 
## Loading required package: Matrix
## 
## Attaching package: 'Matrix'
## 
## The following objects are masked from 'package:tidyr':
## 
##     expand, pack, unpack
## 
## 
## Attaching package: 'gdistance'
## 
## The following object is masked from 'package:igraph':
## 
##     normalize
library(raster)
library(viridis)
## Loading required package: viridisLite
library(RColorBrewer)

First we will get the map of Africa from Natural Earth to use in our plots.

africa_sf <- ne_countries(
  continent = "Africa", 
  scale = "medium",  
  returnclass = "sf" 
)

ggplot(africa_sf) +
  geom_sf() +
  labs(title = "Africa Boundary Map (Natural Earth)") +
  theme_minimal()

Data Analysis and Pre-Processing

africa <- read.csv('data/Africa_Conflict/african_conflicts.csv', fileEncoding = "Windows-1252")
africa
# Naming our dataset
conflict_df <- africa

In our data set we have columns for latitud and longitude, which we will process to extract the coordinates to creat the sf object.

# Getting temporal columns to inspect Latitude and Longitude
df_temp <- conflict_df %>%
  mutate(
    LONGITUDE_num = as.numeric(LONGITUDE),
    LATITUDE_num  = as.numeric(LATITUDE)
  )
## Warning: There were 2 warnings in `mutate()`.
## The first warning was:
## ℹ In argument: `LONGITUDE_num = as.numeric(LONGITUDE)`.
## Caused by warning:
## ! NAs introduced by coercion
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.
# Identifying and counting the rows that have invalid values
invalid_rows <- df_temp %>%
  filter(is.na(LONGITUDE_num) | is.na(LATITUDE_num))

num_invalid <- nrow(invalid_rows)

cat("Number of rows with invalid values in coordinates:", num_invalid, "\n")
## Number of rows with invalid values in coordinates: 13
# Look at problematic values
unique_invalid_long <- unique(invalid_rows$LONGITUDE)
unique_invalid_lat  <- unique(invalid_rows$LATITUDE)

cat("Non convertible LONGITUDE values:\n")
## Non convertible LONGITUDE values:
print(unique_invalid_long)
## [1] "#REF!"      " 27.56061"  "38.69305"   " 31.01736"  "Ê27.56061" 
## [6] "38.6931"    "Ê31.01736"  "11.461101Ê"
cat("Non convertible LATITUDE values:\n")
## Non convertible LATITUDE values:
print(unique_invalid_lat)
## [1] "11.55806"   "-29.39064"  " -15.65777" "-29.82021"  "-29.3906"  
## [6] "Ê-15.65777" "-29.8202"   "33.1399"

We removed this problematic values as they just represented a really small part of our dataset.

# Filter rows without problematic values
df_temp_valid <- df_temp %>%
  filter(!is.na(LONGITUDE_num) & !is.na(LATITUDE_num))

# Replace original columns with clean version
df_temp_valid <- df_temp_valid %>%
  mutate(
    LONGITUDE = LONGITUDE_num,
    LATITUDE  = LATITUDE_num
  ) %>%
  dplyr::select(-LONGITUDE_num, -LATITUDE_num)


# Convert to sf
conflict_sf <- st_as_sf(
  df_temp_valid,
  coords = c("LONGITUDE", "LATITUDE"),
  crs = 4326
)

We will now plot the Map of Conflict Events in Africa

library(ggplot2)
library(sf)

ggplot() +
  geom_sf(data = africa_sf, fill = "grey90", color = "black", size = 0.5) +
  geom_sf(data = conflict_sf, aes(color = FATALITIES), size = 1, alpha = 0.7) +
  labs(title = "Map of Conflict Events in Africa") +
  theme_minimal()

The map presented shows a geospatial visualization of conflict events across Africa from 1997 to 2017. Each point represents an individual conflict event based on the ACLED dataset.

From this initial visualization, we observe an extensive geographic spread of conflict events across the continent. However, it is evident that the majority of events have zero or relatively low fatalities, causing the data points to predominantly appear in dark blue.

To more clearly examine the distribution of high-intensity conflicts, we will next filter the dataset to include only those events associated with at least one fatality. This refined visualization will highlight areas experiencing more severe episodes of violence.

#Filter to only events with > 0 fatalities
conflict_fatalities <- conflict_sf %>%
  filter(FATALITIES > 0)

#Plot with log scale for fatalities
ggplot() +
  geom_sf(data = africa_sf, fill = "grey90", color = "black", size = 0.5) +
  geom_sf(data = conflict_fatalities, aes(color = FATALITIES), size = 1, alpha = 0.7) +
  # Apply a log transform to the color scale
  scale_color_viridis_c(
    trans = "log1p",  # log(1 + x), handles zero or small values better
    breaks = c(1, 10, 100, 1000, 10000, 30000),  # pick sensible breakpoints
    labels = c("1", "10", "100", "1k", "10k", "30k"),  
    na.value = "grey50"  # color for NA values
  ) +
  labs(
    title = "Map of Conflict Events in Africa (Fatalities > 0)",
    color = "Fatalities (log scale)"
  ) +
  theme_minimal()

This map provides a refined visualization focusing exclusively on conflict events with fatalities. Using a logarithmic scale for fatalities, the map reveals clearer spatial patterns that were less evident in the complete dataset.

Several notable clusters of high-fatality events are visible:

These observations suggest the presence of regional conflict dynamics influenced by various social, political, and economic factors.

Country Analysis

To further explore the conflict patterns identified in the initial spatial analysis, we conducted a detailed breakdown at the country level. By summarizing total fatalities and the number of conflict events per country from 1997 to 2017, we aim to reveal country-specific trends and variations in conflict intensity. This deeper exploration facilitates a clearer understanding of the spatial distribution of conflicts, highlighting regions most severely impacted and identifying critical areas.

# Summarize total fatalities and event count by country
summary_by_country <- conflict_sf %>%
  group_by(COUNTRY) %>%
  summarise(total_fatalities = sum(FATALITIES, na.rm = TRUE),
            event_count = n())

print(summary_by_country)
## Simple feature collection with 50 features and 3 fields
## Geometry type: GEOMETRY
## Dimension:     XY
## Bounding box:  xmin: -17.4646 ymin: -34.7101 xmax: 51.2668 ymax: 37.27442
## Geodetic CRS:  WGS 84
## # A tibble: 50 × 4
##    COUNTRY                total_fatalities event_count                  geometry
##    <chr>                             <int>       <int>          <MULTIPOINT [°]>
##  1 Algeria                           13725        5309 ((-1.85 35.1), (-1.85 35…
##  2 Angola                           143881        3181 ((23.0166 -17.5166), (22…
##  3 Benin                                71         187 ((2.4333 6.35), (2.0833 …
##  4 Botswana                              2          53 ((23.4167 -19.9833), (23…
##  5 Burkina Faso                        463        1031 ((-1.32697 14.3358), (-1…
##  6 Burundi                           22655        7396 ((29.7587 -4.4221), (29.…
##  7 Cameroon                           4407         937 ((10.5 6.3333), (9.9264 …
##  8 Central African Repub…            11827        3973 ((14.8787 4.5022), (14.8…
##  9 Chad                               6710         732 ((20.8 9.917), (18.9449 …
## 10 Democratic Republic o…            79255       12294 ((18.35 1.3667), (18.1 2…
## # ℹ 40 more rows

We observe significant variability across countries; Angola and the Democratic Republic of Congo, for instance, display extremely high fatality counts relative to the number of recorded conflict events, indicating particularly severe violence.

To make it more visual we plot the ranking of countries with most conflicts

ggplot(summary_by_country, aes(x = reorder(COUNTRY, -event_count), 
                               y = event_count)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(title = "Total Number of Conflicts by Country",
       x = "Country",
       y = "Number of events") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

This figure ranks African countries according to their total number of recorded conflict events from 1997 to 2017. Somalia stands out prominently, with substantially more conflict events than other countries, followed by the Democratic Republic of Congo and Nigeria.

To further explore the conflicts that have had fatalities we will proceed to make the same summary but with this condition.

# Filter the original conflict data to include only events with > 0 fatalities
conflict_fatalities <- conflict_sf %>%
  filter(FATALITIES > 0)

# Summarize the filtered data by country
summary_by_country_fatal <- conflict_fatalities %>%
  group_by(COUNTRY) %>%
  summarise(
    total_fatalities = sum(FATALITIES, na.rm = TRUE),
    event_count = n()
  )

# Plot the number of events by country (only those with > 0 fatalities)
ggplot(summary_by_country_fatal, aes(x = reorder(COUNTRY, -event_count), y = event_count)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Total Number of Conflicts by Country (Fatalities > 0)",
    x = "Country",
    y = "Number of Events"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

This figure filters conflict events to include only those with at least one fatality. This adjusted analysis underscores Somalia, Nigeria, and Sudan as countries with the highest numbers of fatal conflicts. The patterns highlight regions that have experienced prolonged and severe conflicts, emphasizing the urgency for targeted policy intervention and humanitarian support.

We will now replace the mismatch of names between the Natural Earth’s data and our data to plot it.

#Replacing missmatched names

#Democratic Republic of Congo
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Democratic Republic of Congo"] <- "Democratic Republic of the Congo"
#Republic of Congo
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Republic of Congo"] <- "Republic of the Congo"
#Ivory Coast
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Ivory Coast"] <- "Côte d'Ivoire"
#Swaziland
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Swaziland"] <- "Kingdom of eSwatini"
#Gambia
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Gambia"] <- "The Gambia"
#Gambia
summary_by_country$COUNTRY[summary_by_country$COUNTRY == "Gambia"] <- "The Gambia"

We created two plots, one map of the Conflict Count and one with the Total Fatalities.

# Combining with Conflict Data

# Drop Geometry of summary by country
summary_by_country_df <- summary_by_country %>%
  st_drop_geometry()  # now it's a data frame

africa_joined <- africa_sf %>%
  left_join(summary_by_country_df, by = c("name_long" = "COUNTRY"))

#Conflict Count Map
ggplot(africa_joined) +
  geom_sf(aes(fill = event_count)) +
  scale_fill_viridis_c(option = "plasma", na.value = "grey90") +
  labs(title = "Conflict Events by Country in Africa 1997-2017",
       fill = "Conflicts") +
  theme_minimal()

#Total Fatalities Map
ggplot(africa_joined) +
  geom_sf(aes(fill = total_fatalities)) +
  scale_fill_viridis_c(option = "plasma", na.value = "grey90") +
  labs(title = "Total Fatalities by Country in Africa 1997-2017",
       fill = "Fatalities") +
  theme_minimal()

The first figure illustrates the geographic distribution of total conflict events per country across Africa from 1997 to 2017. Somalia clearly emerges as the leading country in terms of total conflict events, depicted in bright yellow, followed by the Democratic Republic of the Congo, Nigeria, and Sudan, each represented with warmer colors (orange to red). Countries colored in dark blue and purple exhibit significantly fewer conflict events during this period.

In the second map, we visualize total fatalities by country, providing insights into conflict severity. Angola stands out prominently, having the highest fatality count (bright yellow), followed closely by the Democratic Republic of the Congo, Sudan, and Nigeria. Comparing the two maps, it becomes evident that some countries, such as Angola, exhibit fewer total conflicts but higher fatalities, indicating extremely violent conflicts. This distinction underscores the importance of examining both the frequency and intensity of conflicts to fully grasp the complexity of Africa’s conflict landscape.

Time Analysis

In this section, we expand our analysis to explore temporal trends in conflict events and fatalities across Africa from 1997 to 2017. By dividing the data into distinct time intervals, we aim to identify patterns in the frequency and severity of conflicts over time. This temporal perspective allows us to track fluctuations in violence, detect potential periods of escalation or de-escalation, and assess shifts in the spatial distribution of conflict.

#Making a summary by year
summary_by_year <- conflict_sf %>%
  mutate(YEAR = as.numeric(YEAR)) %>%
  group_by(YEAR) %>%
  summarise(total_fatalities = sum(FATALITIES, na.rm = TRUE),
            event_count = n())

print(summary_by_year)
## Simple feature collection with 21 features and 3 fields
## Geometry type: MULTIPOINT
## Dimension:     XY
## Bounding box:  xmin: -17.4646 ymin: -34.7101 xmax: 51.2668 ymax: 37.27442
## Geodetic CRS:  WGS 84
## # A tibble: 21 × 4
##     YEAR total_fatalities event_count                                   geometry
##    <dbl>            <int>       <int>                           <MULTIPOINT [°]>
##  1  1997            54211        3345 ((-2.9287 35.174), (-1.9175 34.6759), (-1…
##  2  1998            70999        4642 ((-13.188 27.1418), (-1.9175 34.6759), (-…
##  3  1999           195181        5008 ((-11.57 25.14), (-13.188 27.1418), (-3.9…
##  4  2000            24383        4244 ((-12.3369 24.9953), (-13.188 27.1418), (…
##  5  2001            27076        3668 ((-11.6719 26.7384), (-5.3729 35.9165), (…
##  6  2002            28846        4481 ((-13.188 27.1418), (-5.419 35.9132), (-1…
##  7  2003            21392        3870 ((-0.3214 35.8517), (-0.6417 35.6911), (-…
##  8  2004            19947        3272 ((-5.8137 35.7806), (-3.9372 35.2517), (-…
##  9  2005             8216        2993 ((-15.9424 23.6927), (-13.188 27.1418), (…
## 10  2006             7873        2740 ((-11.6719 26.7384), (-13.188 27.1418), (…
## # ℹ 11 more rows
#Plotting the trend of Conflicts per Year
ggplot(summary_by_year, aes(x = YEAR, y = event_count)) +
  geom_line(color = "darkred", size = 1) +
  geom_point(color = "darkred", size = 2) +
  labs(title = "Trend of Conflicts by Year",
       x = "Year",
       y = "Number of Conflicts") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

#Plotting the trend of Fatalities per Year
ggplot(summary_by_year, aes(x = YEAR, y = total_fatalities)) +
  geom_line(color = "darkred", size = 1) +
  geom_point(color = "darkred", size = 2) +
  labs(title = "Trend of Fatalities by Year",
       x = "Year",
       y = "Total Fatalities") +
  theme_minimal()

These figures depict the temporal trends in conflict events and fatalities across Africa from 1997 to 2017. The first plot shows a relatively stable number of conflict events between 1997 and 2010, followed by a sharp and sustained increase from 2011 onward. The most dramatic spike occurs around 2015-2016, reaching over 30,000 recorded conflicts in a single year, before dropping sharply in 2017. This surge may be linked to an escalation in armed insurgencies, political instability, or intensified reporting mechanisms.

The second plot illustrates the number of fatalities over the same period. Unlike conflict events, fatalities show a different pattern, with a major spike around 1999-2000, exceeding 200,000 deaths, likely tied to major wars or large-scale violence in that period. After a significant decline post-2000, fatalities remain relatively stable with periodic fluctuations. However, from 2010 onwards, fatalities begin to increase again, suggesting that while conflict frequency rose, not all conflicts were equally deadly.

The contrast between the two trends suggests that conflict frequency has risen dramatically in recent years, but the deadliest periods may have been in earlier years, possibly driven by large-scale wars rather than smaller but more frequent conflicts in later years.

Temporal Patterns of Conflict Events and Fatalities in 5-Year Intervals

Next, we segment the data into five-year intervals to observe how both the number of conflict events and their associated fatalities evolve over broader time spans.

conflict_sf <- conflict_sf %>%
  mutate(
    year_bin = cut(
      YEAR,
      breaks = c(1997, 2001, 2005, 2009, 2013, 2018),
      right = FALSE,
      labels = c("1997-2000", "2001-2004", "2005-2008", "2009-2012", "2013-2017")
    )
  )

ggplot(conflict_sf) +
  # Base map (africa_sf), no faceting needed, so disable inherited aesthetics
  geom_sf(data = africa_sf, fill = "grey90", color = "black", inherit.aes = FALSE) +
  # Conflict events, colored by FATALITIES
  geom_sf(aes(color = FATALITIES), size = 1, alpha = 0.7) +
  # Facet by year_bin
  facet_wrap(~ year_bin) +
  # Log scale color
  scale_color_viridis_c(
    trans = "log1p",
    breaks = c(0, 1, 10, 100, 1000, 10000, 30000),  
    labels = c("0", "1", "10", "100", "1k", "10k", "30k"),
    na.value = "grey50"
  ) +
  labs(
    title = "Conflict Events by 5-Year Intervals",
    color = "Fatalities (log scale)"
  ) +
  theme_minimal()

To further analyze the evolution of conflicts in Africa over time, we segmented the data into five-year intervals. The figure illustrates how both the number of conflict events and their associated fatalities have evolved over broader time spans.

Overall, these maps indicate that while conflict has persisted across Africa throughout these two decades, the intensity and distribution of high-fatality incidents have shifted. Early periods (1997-2000) exhibit fewer overall conflict events but a concentration of high-fatality incidents. In contrast, later periods (2013-2017) show a higher density of conflicts but with relatively lower fatalities per event. This suggests a shift in the nature of conflicts, possibly due to changes in warfare tactics, governance structures, or international interventions.

We will make this plot again but now filtering for the conflicts that have had fatalities.

# Filter out zero-fatality events
conflict_fatal_sf <- conflict_sf %>%
  filter(FATALITIES > 0)

ggplot() +
  # Base map
  geom_sf(data = africa_sf, fill = "grey90", color = "black") +
  # Only plot events with > 0 fatalities
  geom_sf(data = conflict_fatal_sf, aes(color = FATALITIES), size = 1, alpha = 0.7) +
  facet_wrap(~ year_bin) +
  # Log scale with custom breaks to reduce label clutter
  scale_color_viridis_c(
    trans = "log1p",
    breaks = c(1, 10, 100, 1000, 10000, 30000),
    labels = c("1", "10", "100", "1k", "10k", "30k"),
    na.value = "grey50"
  ) +
  labs(
    title = "Conflict Events (Fatalities > 0) by 5-Year Intervals",
    color = "Fatalities (log scale)"
  ) +
  theme_minimal()

By plotting only conflicts that resulted in at least one fatality, these maps focus on more severe incidents. This approach reduces clutter and emphasizes regions with significant violence. Despite persistent conflict across Africa over the two decades, the intensity and distribution of deadly events have shifted over time. We can clearly see how in the first interval although less conflicts exist, there are more fatalities, while in the last ones we see more conflict but less fatalities.

Combining conflict and mineral resources information

Now, we will try to combine both information. So we add the minerals information to our conflict_sf.

# Assuming minerals is a data.frame with columns: longitude, latitude, DsgAttr02
minerals_sf <- st_as_sf(minerals_conflict, coords = c("Longitude", "Latitude"), crs = 4326)  # WGS84

# Convert to 2D (XY only)
minerals_sf <- st_zm(minerals_sf, drop = TRUE, what = "ZM")

# Optional: preview
head(minerals_sf)
# Plot without legend
ggplot() +
  geom_sf(data = Africa, fill = "lightblue", color = "darkgray", alpha = 0.8) +  # Base map
  geom_sf(data = minerals_sf, aes(color = DsgAttr02), size = 2, alpha = 0.8) +  # Mineral points
  theme_minimal() +
  labs(
    title = "Valuable Mineral Resource Sites in Africa",
    subtitle = "Colored by Resource Type (DsgAttr02_new)"
  ) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    legend.position = "none"
  )

minerals_conflict %>%
  st_drop_geometry() %>%  # Remove spatial part to speed up
  group_by(Country, DsgAttr02) %>%
  summarise(count = n(), .groups = "drop") %>%
  arrange(desc(count))

We start by visualizing the minerals and conflicts together

ggplot() +
  geom_sf(data = africa_sf, fill = "antiquewhite", color = "grey40") +
  geom_sf(data = minerals_conflict, color = "red", size = 1, alpha = 0.6) +
  geom_sf(data = conflict_sf, color = "black", size = 0.8, alpha = 0.5) +
  labs(title = "Conflict Events and Mineral Sites in Africa",
       subtitle = "Minerals associated with conflict shown in red") +
  theme_minimal()

Black dots (conflict events) are widespread, but denser in certain regions. The DRC, Nigeria, Sudan, and the Horn of Africa appear as hotspots for conflict. Red dots (minerals) are scattered across the continent, with notable clustering in Central Africa, West Africa, and parts of South Africa. There is visible overlap between areas of high conflict intensity and mineral locations, especially in regions like the eastern DRC and the Sahel.

Potential Hotspots for Resource-Driven Conflict:

Conflicts Colored by Closest Mineral Source

We now color conflict points based on the closest mineral source by assigning each conflict event the mineral type of its nearest deposit. This will allow us to visualize which conflicts are most associated with different resources

We find the nearest mineral for each conflict using st_nearest_feature().

# Find the nearest mineral for each conflict
nearest_mineral_index <- st_nearest_feature(conflict_sf, minerals_conflict)
## st_as_s2(): dropping Z and/or M coordinate
# Assign the closest mineral type to each conflict event
conflict_sf$closest_mineral <- minerals_conflict$DsgAttr02[nearest_mineral_index]

# Define a more contrasting color palette
mineral_colors <- c("Bauxite" = "#984ea3", "Cobalt" = "#377eb8", "Copper" = "#e41a1c", 
                    "Diamond" = "#ff7f00", "Gold" = "#FFD700", "Natural gas" = "#4daf4a", 
                    "Nickel" = "#a65628", "Petroleum" = "#191970", "Tantalum" = "#f781bf", 
                    "Tin" = "#999999", "Tungsten" = "#dede00", "Uranium" = "#00CED1")

# Create the plot
ggplot() +
  geom_sf(data = africa_sf, fill = "antiquewhite", color = "grey40") +  # Africa background
  geom_sf(data = conflict_sf, aes(color = closest_mineral), size = 1.2, alpha = 0.8) +  # Conflicts colored by mineral type
  geom_sf(data = minerals_conflict, shape = 17, size = 1.5, color = "black", alpha = 0.5) +  # Mineral locations
  scale_color_manual(values = mineral_colors, name = "Closest Mineral") +  # Custom colors
  labs(title = "Conflicts Colored by Closest Mineral Source",
       subtitle = "Each conflict event is colored by the nearest mineral deposit",
       caption = "Black triangles = Mineral deposits, Colored points = Conflict events") +
  theme_minimal() +
  theme(legend.position = "right",
        axis.text = element_blank(),
        axis.ticks = element_blank())

The image effectively visualizes conflicts in Africa, color-coded by their closest mineral deposit, allowing us to observe spatial patterns in mineral-related conflicts. The black triangles represent mineral deposit locations, while the colored points indicate conflicts, showing which minerals are geographically associated with violence. There is a high density of conflicts near petroleum (dark blue), gold (yellow), and tantalum (pink) deposits, suggesting that these minerals may be more strongly linked to conflict. In West Africa and the Great Lakes region (DRC, Rwanda, Uganda), many conflicts appear tied to tantalum and gold, both known to fuel armed groups. The Sahel region (such as Mali, Chad, Sudan) shows substantial conflict near uranium and petroleum deposits, potentially linking these resources to insurgencies. Overall, the map succeed in highlighting how different mineral resources may be geographically linked to different types of conflicts, warranting further investigation into causality and economic incentives.

Distance to Nearest Mineral Site

We now perform a spatial analysis to investigate the relationship between conflict events and their proximity to mineral deposits, especially focusing on whether conflicts are clustered around valuable resources.To do so, we reproject the data to a Projected CRS for Distance/Buffer Ops

# Use a metric CRS 
proj_crs <- 3395  # Just use the numeric EPSG code

# Transform both datasets to the projected CRS
minerals_proj <- st_transform(minerals_conflict, crs = proj_crs)
conflicts_proj <- st_transform(conflict_sf, crs = proj_crs)

We then create Buffer Zones Around Mineral Sites (50 km). st_buffer() creates 50 km radius zones around each mineral site. These represent areas of potential influence.

# Convert to 2D (XY) format by dropping extra dimensions
minerals_proj <- st_zm(minerals_proj, drop = TRUE, what = "ZM")

# Now apply the buffer without errors
buffer_50km <- st_buffer(minerals_proj, dist = 50000)  # 50,000 meters = 50km

Spatial Join: It calculates how many conflicts there are within 50 km of any mineral deposit. It then outputs both the count and percentage of conflicts near minerals, which is useful for suggesting spatial correlation.

conflicts_in_buffer <- st_join(conflicts_proj, buffer_50km, left = FALSE)

# Basic stat
total_conflicts <- nrow(conflicts_proj)
near_conflicts <- nrow(conflicts_in_buffer)
percent_near <- near_conflicts / total_conflicts * 100

cat("Conflicts within 50km of a conflict mineral site:", near_conflicts, 
    "out of", total_conflicts, "(", round(percent_near, 2), "% )\n")
## Conflicts within 50km of a conflict mineral site: 147605 out of 165795 ( 89.03 % )

Add Distance to Nearest Mineral Site (For each conflict, it finds the closest mineral site).

# Calculate distance from each conflict to the nearest mineral site
distance_matrix <- st_distance(conflicts_proj, minerals_proj)
conflicts_proj$dist_to_mineral <- apply(distance_matrix, 1, min)

# Plot histogram of distances
ggplot(conflicts_proj, aes(x = dist_to_mineral / 1000)) +  # Convert to km
  geom_histogram(bins = 50, fill = "steelblue") +
  labs(title = "Distance to Nearest Mineral Site",
       x = "Distance (km)", y = "Number of Conflicts") +
  theme_minimal()

Most conflicts occur within 100 km of a mineral site, indicating strong spatial clustering. The frequency of conflicts decreases steadily as distance increases. A notable spike appears around 900 km, suggesting a potential spatial or data anomaly. A long tail shows conflicts still occur far from mineral sites, implying other conflict drivers. Overall, the plot suppert a strong concentration of conflicts near mineral resources, potentially supporting hypotheses about resource-driven violence or competition.

Logistic Regression: Does Proximity to Minerals Predict Conflict?

To answer our research question, we will estimate a logistic regression where the dependent variable is whether a conflict occurs in a given spatial unit, and the independent variable is the distance to the nearest mineral deposit.

Steps: - Create a spatial grid over Africa. - Assign each grid cell: a conflict occurrence (1 or 0) - Distance to the nearest mineral deposit. - Run a logistic regression.

# Reproject Africa's shapefile to an appropriate CRS for distance calculations (meters)
africa_proj <- st_transform(africa_sf, 3857)  # Web Mercator

# Create hexagonal grid over Africa (100 km resolution)
grid_sf <- st_make_grid(africa_proj, cellsize = 100000, what = "polygons", square = FALSE) %>%
           st_sf()

# Plot the grid and Africa boundary
ggplot() +
  geom_sf(data = grid_sf, fill = NA, color = "black", size = 0.3) +
  geom_sf(data = africa_proj, fill = NA, color = "red", size = 0.5) +
  labs(title = "Hexagonal Grid Over Africa (100 km Resolution)") +
  theme_minimal()

# Ensure CRS matches (transform conflicts and minerals)
conflict_sf <- st_transform(conflict_sf, st_crs(grid_sf))
minerals_conflict <- st_transform(minerals_conflict, st_crs(grid_sf))
# Count number of conflicts per grid cell
conflict_counts <- st_intersects(grid_sf, conflict_sf)  # Find which cells contain conflicts

# Assign a conflict count per grid cell
grid_sf$conflict_count <- lengths(conflict_counts)  # Get number of conflicts per cell

# Create binary variable (1 if at least one conflict, else 0)
grid_sf$conflict <- ifelse(grid_sf$conflict_count > 0, 1, 0)

# Remove M/Z dimensions from grid_sf before computing centroids
grid_sf <- st_zm(grid_sf)
minerals_conflict <- st_zm(minerals_conflict)

# Now compute centroids safely
grid_centroids <- st_centroid(grid_sf)
## Warning: st_centroid assumes attributes are constant over geometries
# Compute distance to nearest mineral site
dist_matrix <- st_distance(grid_centroids, minerals_conflict)

# Ensure there are no empty distance values
dist_matrix[is.infinite(dist_matrix)] <- NA

# Take the minimum distance for each grid cell and convert to km
grid_sf$dist_to_mineral <- apply(dist_matrix, 1, min, na.rm = TRUE) / 1000  # Convert meters to km

# Run logistic regression (Conflict Presence ~ Distance to Mineral)
logit_model <- glm(conflict ~ dist_to_mineral, data = grid_sf, family = "binomial")

# Show results
summary(logit_model)
## 
## Call:
## glm(formula = conflict ~ dist_to_mineral, family = "binomial", 
##     data = grid_sf)
## 
## Coefficients:
##                   Estimate Std. Error z value Pr(>|z|)    
## (Intercept)      0.8752688  0.0468119   18.70   <2e-16 ***
## dist_to_mineral -0.0046090  0.0001351  -34.12   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 11296.3  on 10552  degrees of freedom
## Residual deviance:  7370.1  on 10551  degrees of freedom
## AIC: 7374.1
## 
## Number of Fisher Scoring iterations: 8

The logistic regression results show a significant negative relationship between distance to minerals and conflict presence. The intercept value of 0.8753 indicates that at zero distance (right at a mineral site), the odds of conflict are high. For each additional distance away, the odds of conflict decrease by approximately 0.5%. Over a 100 km distance, the odds of conflict drop by nearly 40%, highlighting the strong spatial correlation between natural resources and violence. The coefficient for distance is highly significant with a p-value < 2e-16. The model explains a substantial portion of variation in conflict occurrence, as seen by the reduction in deviance. The low AIC (7374.1) further confirms the model’s good fit. These findings provide compelling evidence that resource wealth attracts conflict, reinforcing the idea that mineral deposits are strategic assets in armed struggles.

The logistic regression suggests proximity to minerals predicts the likelihood of conflict.

However, it’s also possible that mineral-rich areas are more densely populated or strategically important, which could independently increase the likelihood of conflict compared to remote or sparsely populated regions like deserts. Additionally, since the analysis used a 50 km buffer around mineral sites, reducing the buffer size might yield different results, potentially making the relationship more or less pronounced depending on local conditions. A next step for future resources could involve controlling for population density or infrastructure, and comparing effects across different mineral types to assess whether certain resources, like gold or uranium, are more strongly associated with violence.

Comparing Conflict Frequency for Gold, Cobalt, and Petroleum

# 1. Filter minerals dataset for Gold, Cobalt, and Petroleum
gold_mines <- minerals_conflict %>% filter(DsgAttr02 == "Gold")
cobalt_mines <- minerals_conflict %>% filter(DsgAttr02 == "Cobalt")
petroleum_sites <- minerals_conflict %>% filter(DsgAttr02 == "Petroleum")

# 2. Compute distance from each conflict to the nearest site of each mineral
dist_gold <- apply(st_distance(conflict_sf, gold_mines), 1, min, na.rm = TRUE) / 1000  # Convert meters to km
dist_cobalt <- apply(st_distance(conflict_sf, cobalt_mines), 1, min, na.rm = TRUE) / 1000
dist_petroleum <- apply(st_distance(conflict_sf, petroleum_sites), 1, min, na.rm = TRUE) / 1000

# 3. Create a dataframe for visualization
dist_df <- data.frame(
  distance_km = c(dist_gold, dist_cobalt, dist_petroleum),
  mineral = rep(c("Gold", "Cobalt", "Petroleum"), each = length(dist_gold))
)

# 4. Plot distribution of conflict distances to each mineral type
ggplot(dist_df, aes(x = distance_km, fill = mineral)) +
  geom_density(alpha = 0.6) +
  labs(title = "Distribution of Conflict Distance to Gold, Cobalt, and Petroleum",
       x = "Distance to Nearest Mineral Site (km)", y = "Density") +
  scale_fill_manual(values = c("Gold" = "goldenrod", "Cobalt" = "blue", "Petroleum" = "black")) +
  theme_minimal()

The graph shows the distribution of distances from conflict events to the nearest deposits of Gold, Cobalt, and Petroleum in Africa.

The black (Petroleum) and gold (Gold) curves have high density at very short distances (left side of the plot), meaning many conflict events occur very close to these mineral deposits. This suggests that gold and petroleum are more spatially linked to conflict compared to cobalt.

The blue (Cobalt) curve is more spread out and has peaks at larger distances. This suggests that conflicts are less likely to occur near cobalt deposits compared to gold or petroleum.

The gold and petroleum distributions have steep peaks at very close distances, indicating direct spatial clustering of conflicts around these deposits. The cobalt distribution is more spread out, with peaks appearing farther from zero, suggesting conflicts tend to occur at varying distances from cobalt deposits rather than being tightly clustered.

The overlap between the black (petroleum) and gold (gold) curves at short distances suggests that both of these resources may attract similar types of conflict dynamics. This aligns with the fact that gold mining and oil extraction are major economic drivers, often linked to armed struggles.

Conflict Intensity: Do areas with more abundant or higher-value resources experience more severe or persistent violence?

We now perform a spatial analysis to count and visualize how many conflicts occur within 10 km of each type of mineral deposit. Our goal is to identify which mineral types are most frequently associated with nearby conflicts, based on a 10 km buffer. It offers insights into whether certain resources, like gold or diamonds, are more strongly linked to violence than others.

# Ensure same CRS
conflict_sf <- st_transform(conflict_sf, st_crs(minerals_conflict))

# Create 10 km buffer around minerals
minerals_buffer <- st_buffer(minerals_conflict, dist = 10000)

# Spatial join: Assign conflicts to the nearest mineral type
conflicts_per_mineral <- st_join(conflict_sf, minerals_buffer, left = FALSE)

# Filter out NA values (conflicts that didn't join with any mineral)
conflicts_per_mineral <- conflicts_per_mineral %>%
  filter(!is.na(DsgAttr02)) %>%
  group_by(DsgAttr02) %>%
  summarise(conflict_count = n())

# Plot conflict frequency per mineral type
ggplot(conflicts_per_mineral, aes(x = DsgAttr02, y = conflict_count, fill = DsgAttr02)) +
  geom_bar(stat = "identity") +
  labs(title = "Conflict Frequency by Mineral Type",
       x = "Mineral Type", y = "Number of Conflicts") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # Rotate labels for readability

  1. Petroleum Has the Highest Conflict Association:

The largest number of conflicts is linked to Petroleum, suggesting that oil-rich regions are highly prone to conflict. This aligns with historical patterns where oil wealth has been linked to political instability, insurgencies, and resource-based wars (as in Nigeria, Sudan, Libya).

  1. Gold and Copper Also Show High Conflict Frequency:

Gold and Copper also have a strong association with conflict. Gold-related conflicts could be due to illegal mining, smuggling, and armed groups financing operations via gold sales (such as in Democratic Republic of Congo, Mali, Sudan). Copper mining regions may experience disputes over resource control and environmental issues.

  1. Cobalt Shows Moderate Conflict Association:

Cobalt, critical for battery production, has a significant conflict count but lower than gold or petroleum. The DRC, which holds ~70% of global cobalt reserves, has documented cases of child labor, exploitation, and militia involvement in cobalt extraction.

  1. Diamonds Have Relatively Lower Conflict Counts:

Unlike historical “Blood Diamond” conflicts (e.g., Sierra Leone, Angola), the chart suggests that modern conflicts are less associated with diamonds compared to oil or gold. This could reflect increased international regulations limiting illegal diamond trade for conflict financing.

  1. Minerals with Minimal Conflict Association:

Tungsten, Uranium, Bauxite, and Nickel show much lower conflict counts. This suggests these resources may not be as strategically contested or have stronger governance controls.

Mapping Conflict Intensity by Mineral Region

To visualize the most violent areas, we’ll:

Aggregate the number of conflicts per mineral region. Create a choropleth (heatmap) or proportional symbol map to show where conflicts are most concentrated. Overlay conflict events and mineral locations to compare conflict hotspots.

# Ensure CRS consistency for all spatial datasets
conflict_sf <- st_transform(conflict_sf, st_crs(minerals_conflict))
africa_sf <- st_transform(africa_sf, st_crs(minerals_conflict))

# Create a buffer around each mineral site (e.g., 50 km) to define "mineral regions"
mineral_regions <- st_buffer(minerals_conflict, dist = 50000)

# Count number of conflicts per mineral region
conflict_intensity <- st_join(mineral_regions, conflict_sf) %>%
  group_by(DsgAttr02) %>%
  summarise(conflict_count = n(), .groups = "drop")

# Merge conflict counts back into mineral regions (spatial join)
mineral_regions <- st_join(mineral_regions, conflict_intensity, left = TRUE)

# Replace NA values in conflict_count with 0 (for mineral regions with no conflicts)
mineral_regions$conflict_count[is.na(mineral_regions$conflict_count)] <- 0

# Plot: Conflict Intensity by Mineral Region with Africa's Country Borders
ggplot() +
  geom_sf(data = africa_sf, fill = "antiquewhite", color = "grey40") +  # Add Africa map
  geom_sf(data = mineral_regions, aes(fill = conflict_count), color = "black") +  # Mineral regions
  scale_fill_viridis_c(option = "magma", name = "Conflict Intensity") +
  labs(title = "Conflict Intensity by Mineral Region",
       subtitle = "Darker areas indicate regions near minerals with high conflict counts",
       caption = "Black outlines = country borders") +
  theme_minimal()

Many conflicts appear concentrated near mineral sites, suggesting a strong spatial relationship between resource wealth and violence. - The Sahel region (Mali, Niger, Chad, Sudan) shows high conflict intensity, likely due to political instability, insurgencies, and access to resources like gold, uranium, and oil. - Democratic Republic of the Congo appears densely covered, aligning with ongoing conflicts related to cobalt, gold, and coltan mining. - Nigeria’s Niger Delta shows high conflict intensity, consistent with oil-related violence and insurgencies. - West African coastal nations (such as Nigeria, Ghana, Côte d’Ivoire) show high conflict intensity near oil and gold sites.

Does Resource Type Predict Conflict Deaths?

This approach will test whether certain minerals (oil, gold, etc.) are associated with higher casualties per conflict.

# Ensure CRS consistency before joining
conflict_sf <- st_transform(conflict_sf, st_crs(minerals_buffer))

# Now, perform the spatial join
conflict_mineral <- st_join(conflict_sf, minerals_buffer, left = FALSE)

# Verify that the join worked
table(conflict_mineral$DsgAttr02)  
## 
##     Arsenic     Bauxite      Cobalt      Copper     Diamond        Gold 
##           0          21        2545        4303         756        4146 
## Natural gas      Nickel   Petroleum    Tantalum         Tin    Tungsten 
##         528         742        6362         451        1502         319 
##     Uranium 
##          59
# Run linear regression: Do some minerals predict higher fatalities?
lm_model <- lm(FATALITIES ~ DsgAttr02, data = conflict_mineral)
summary(lm_model)
## 
## Call:
## lm(formula = FATALITIES ~ DsgAttr02, data = conflict_mineral)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
##  -9.95  -1.35  -1.29  -0.60 797.20 
## 
## Coefficients:
##                      Estimate Std. Error t value Pr(>|t|)   
## (Intercept)           0.61905    3.32880   0.186  0.85247   
## DsgAttr02Cobalt      -0.01787    3.34250  -0.005  0.99573   
## DsgAttr02Copper       0.25313    3.33691   0.076  0.93953   
## DsgAttr02Diamond      0.10185    3.37472   0.030  0.97592   
## DsgAttr02Gold         2.18028    3.33722   0.653  0.51356   
## DsgAttr02Natural gas  0.73323    3.39435   0.216  0.82898   
## DsgAttr02Nickel      -0.32794    3.37558  -0.097  0.92261   
## DsgAttr02Petroleum    0.67048    3.33429   0.201  0.84063   
## DsgAttr02Tantalum     8.17696    3.40542   2.401  0.01635 * 
## DsgAttr02Tin          2.81105    3.35199   0.839  0.40169   
## DsgAttr02Tungsten     9.33080    3.43662   2.715  0.00663 **
## DsgAttr02Uranium     -0.21227    3.87620  -0.055  0.95633   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.25 on 21722 degrees of freedom
## Multiple R-squared:  0.01235,    Adjusted R-squared:  0.01185 
## F-statistic:  24.7 on 11 and 21722 DF,  p-value: < 2.2e-16

The linear regression tests whether fatalities differ by mineral type. Most minerals show no significant effect on fatalities compared to the baseline. However, Tantalum and Tungsten are significantly associated with higher fatality counts. Tantalum increases fatalities by ~8.18 and Tungsten by ~9.33 on average, both statistically significant. The overall model fit is low (R² = 0.012), indicating mineral type explains little of the variance in fatalities. This suggests some minerals may be linked to more violent conflicts, but other factors likely play a larger role.

Conclusions

Our analysis reveals a strong spatial relationship between mineral deposits and conflict events, with most conflicts occurring within 100 km of mineral sites. Logistic regression confirms that proximity to minerals significantly increases the likelihood of conflict, with odds dropping by ~40% over 100 km. A 10 km buffer analysis shows that certain minerals, like gold and petroleum, are more frequently associated with nearby conflicts. However, fatalities per conflict vary less consistently across mineral types. Only Tantalum and Tungsten are significantly linked to higher fatality counts. While minerals appear to attract conflict, population density, strategic location, and other contextual factors may also influence these patterns and warrant further analysis. Together, the findings support the role of resource proximity in shaping where conflicts occur, while highlighting the need for further investigation into what drives their intensity.